home *** CD-ROM | disk | FTP | other *** search
/ The Atari Compendium / The Atari Compendium (Toad Computers) (1994).iso / files / prgtools / mint / mint96sb.zoo / src / procfs.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-10-10  |  15.0 KB  |  653 lines

  1. /*
  2. Copyright 1991,1992 Eric R. Smith. All rights reserved.
  3.  */
  4.  
  5. /* PROC pseudo-filesystem routines */
  6. /* basically just to allow 'ls -l X:' to give a list of active processes
  7.  * some things to note:
  8.  * process names are given as name.XXX, where 'XXX' is the pid of the
  9.  *   process
  10.  * process attributes depend on the run queue as follows:
  11.  *   RUNNING:    0x00        (normal)
  12.  *   READY:    0x01        (read-only)
  13.  *   WAIT:    0x20        (archive bit)
  14.  *   IOBOUND:    0x21        (archive bit+read-only)
  15.  *   ZOMBIE:    0x22        (archive+hidden)
  16.  *   TSR:    0x02        (hidden)
  17.  *   STOP:    0x24        (archive bit+system)
  18.  * the general principle is: inactive processes have the archive bit (0x20)
  19.  * set, terminated processes have the hidden bit (0x02) set, stopped processes
  20.  * have the system bit (0x04) set, and the read-only bit is used to
  21.  * otherwise distinguish states (which is unfortunate, since it would be
  22.  * nice if this bit corresponded with file permissions).
  23.  */
  24.  
  25. #include "mint.h"
  26.  
  27.  
  28. static long    ARGS_ON_STACK proc_root    P_((int drv, fcookie *fc));
  29. static long    ARGS_ON_STACK proc_lookup    P_((fcookie *dir, const char *name, fcookie *fc));
  30. static long    ARGS_ON_STACK proc_getxattr    P_((fcookie *fc, XATTR *xattr));
  31. static long    ARGS_ON_STACK proc_chattr    P_((fcookie *fc, int attrib));
  32. static long    ARGS_ON_STACK proc_chown    P_((fcookie *fc, int uid, int gid));
  33. static long    ARGS_ON_STACK proc_chmode    P_((fcookie *fc, unsigned mode));
  34. static long    ARGS_ON_STACK proc_rmdir    P_((fcookie *dir, const char *name));
  35. static long    ARGS_ON_STACK proc_remove    P_((fcookie *dir, const char *name));
  36. static long    ARGS_ON_STACK proc_getname    P_((fcookie *root, fcookie *dir, char *pathname));
  37. static long    ARGS_ON_STACK proc_rename    P_((fcookie *olddir, char *oldname,
  38.                     fcookie *newdir, const char *newname));
  39. static long    ARGS_ON_STACK proc_opendir    P_((DIR *dirh, int flags));
  40. static long    ARGS_ON_STACK proc_readdir    P_((DIR *dirh, char *nm, int nmlen, fcookie *));
  41. static long    ARGS_ON_STACK proc_rewinddir    P_((DIR *dirh));
  42. static long    ARGS_ON_STACK proc_closedir    P_((DIR *dirh));
  43. static long    ARGS_ON_STACK proc_pathconf    P_((fcookie *dir, int which));
  44. static long    ARGS_ON_STACK proc_dfree    P_((fcookie *dir, long *buf));
  45. static DEVDRV *    ARGS_ON_STACK proc_getdev    P_((fcookie *fc, long *devsp));
  46.  
  47. static long    ARGS_ON_STACK proc_open    P_((FILEPTR *f));
  48. static long    ARGS_ON_STACK proc_write    P_((FILEPTR *f, const char *buf, long bytes));
  49. static long    ARGS_ON_STACK proc_read    P_((FILEPTR *f, char *buf, long bytes));
  50. static long    ARGS_ON_STACK proc_lseek    P_((FILEPTR *f, long where, int whence));
  51. static long    ARGS_ON_STACK proc_ioctl    P_((FILEPTR *f, int mode, void *buf));
  52. static long    ARGS_ON_STACK proc_datime    P_((FILEPTR *f, short *time, int rwflag));
  53. static long    ARGS_ON_STACK proc_close    P_((FILEPTR *f, int pid));
  54.  
  55. /* dummy routines from biosfs.c */
  56. extern long    ARGS_ON_STACK null_select    P_((FILEPTR *f, long p, int mode));
  57. extern void    ARGS_ON_STACK null_unselect    P_((FILEPTR *f, long p, int mode));
  58.  
  59. static PROC *    name2proc    P_((const char *name));
  60.  
  61.  
  62. DEVDRV proc_device = {
  63.     proc_open, proc_write, proc_read, proc_lseek, proc_ioctl, proc_datime,
  64.     proc_close, null_select, null_unselect
  65. };
  66.  
  67. FILESYS proc_filesys = {
  68.     (FILESYS *)0,
  69.     0,
  70.     proc_root,
  71.     proc_lookup, nocreat, proc_getdev, proc_getxattr,
  72.     proc_chattr, proc_chown, proc_chmode,
  73.     nomkdir, proc_rmdir, proc_remove, proc_getname, proc_rename,
  74.     proc_opendir, proc_readdir, proc_rewinddir, proc_closedir,
  75.     proc_pathconf, proc_dfree,
  76.     nowritelabel, noreadlabel, nosymlink, noreadlink, nohardlink,
  77.     nofscntl, nodskchng
  78. };
  79.  
  80. long ARGS_ON_STACK 
  81. proc_root(drv, fc)
  82.     int drv;
  83.     fcookie *fc;
  84. {
  85.     if (drv == PROCDRV) {
  86.         fc->fs = &proc_filesys;
  87.         fc->dev = drv;
  88.         fc->index = 0L;
  89.         return 0;
  90.     }
  91.     fc->fs = 0;
  92.     return EINTRN;
  93. }
  94.  
  95. static PROC *
  96. name2proc(name)
  97.     const char *name;
  98. {
  99.     const char *pstr;
  100.     char c;
  101.     int i;
  102.  
  103.     pstr = name;
  104.     while ( (c = *name++) != 0) {
  105.         if (c == '.')
  106.             pstr = name;
  107.     }
  108.     if (!isdigit(*pstr) && *pstr != '-')
  109.         return 0;
  110.     i = (int)atol(pstr);
  111.     if (i == -1)
  112.         return curproc;
  113.     else if (i == -2)
  114.         i = curproc->ppid;
  115.     return pid2proc(i);
  116. }
  117.  
  118. static long ARGS_ON_STACK 
  119. proc_lookup(dir, name, fc)
  120.     fcookie *dir;
  121.     const char *name;
  122.     fcookie *fc;
  123. {
  124.     PROC *p;
  125.  
  126.     if (dir->index != 0) {
  127.         DEBUG(("proc_lookup: bad directory"));
  128.         return EPTHNF;
  129.     }
  130.  
  131. /* special case: an empty name in a directory means that directory */
  132. /* so does "." */
  133.     if (!*name || (name[0] == '.' && name[1] == 0)) {
  134.         *fc = *dir;
  135.         return 0;
  136.     }
  137.  
  138. /* another special case: ".." could be a mount point */
  139.     if (!strcmp(name, "..")) {
  140.         *fc = *dir;
  141.         return EMOUNT;
  142.     }
  143.  
  144.     if (0 == (p = name2proc(name))) {
  145.         DEBUG(("proc_lookup: name not found"));
  146.         return EFILNF;
  147.     } else {
  148.         fc->index = (long)p;
  149.         fc->fs = &proc_filesys;
  150.         fc->dev = PROC_BASE_DEV | p->pid;
  151.     }
  152.     return 0;
  153. }
  154.  
  155. static int p_attr[NUM_QUEUES] = {    /* attributes corresponding to queues */
  156.     0,            /* "RUNNING" */
  157.     0x01,            /* "READY" */
  158.     0x20,            /* "WAITING" */
  159.     0x21,            /* "IOBOUND" */
  160.     0x22,            /* "ZOMBIE" */
  161.     0x02,            /* "TSR" */
  162.     0x24,            /* "STOPPED" */
  163.     0x21            /* "SELECT" (same as IOBOUND) */
  164. };
  165.  
  166. static long ARGS_ON_STACK 
  167. proc_getxattr(fc, xattr)
  168.     fcookie *fc;
  169.     XATTR *xattr;
  170. {
  171.     PROC *p;
  172.     extern int proctime, procdate;    /* see dosmem.c */
  173.  
  174.     xattr->blksize = 1;
  175.     if (fc->index == 0) {
  176.         /* the root directory */
  177.         xattr->index = 0;
  178.         xattr->dev = PROCDRV;
  179.         xattr->nlink = 1;
  180.         xattr->uid = xattr->gid = 0;
  181.         xattr->size = xattr->nblocks = 0;
  182.         xattr->mtime = xattr->atime = xattr->ctime = proctime;
  183.         xattr->mdate = xattr->adate = xattr->cdate = procdate;
  184.         xattr->mode = S_IFDIR | DEFAULT_DIRMODE;
  185.         xattr->attr = FA_DIR;
  186.         return 0;
  187.     }
  188.  
  189.     p = (PROC *)fc->index;
  190.     xattr->index = p->pid;
  191.     xattr->dev = PROC_BASE_DEV | p->pid;
  192.     xattr->nlink = 1;
  193.     xattr->uid = p->ruid; xattr->gid = p->rgid;
  194.     xattr->size = xattr->nblocks = memused(p);
  195.     xattr->mtime = xattr->ctime = xattr->atime = p->starttime;
  196.     xattr->mdate = xattr->cdate = xattr->adate = p->startdate;
  197.     xattr->mode = S_IMEM | S_IRUSR | S_IWUSR;
  198.     xattr->attr = p_attr[p->wait_q];
  199.     return 0;
  200. }
  201.  
  202. static long ARGS_ON_STACK 
  203. proc_chattr(fc, attrib)
  204.     fcookie *fc;
  205.     int attrib;
  206. {
  207.     UNUSED(fc); UNUSED(attrib);
  208.  
  209.     return EACCDN;
  210. }
  211.  
  212. static long ARGS_ON_STACK 
  213. proc_chown(fc, uid, gid)
  214.     fcookie *fc;
  215.     int uid, gid;
  216. {
  217.     UNUSED(fc); UNUSED(uid); UNUSED(gid);
  218.     return EINVFN;
  219. }
  220.  
  221. static long ARGS_ON_STACK 
  222. proc_chmode(fc, mode)
  223.     fcookie *fc;
  224.     unsigned mode;
  225. {
  226.     UNUSED(fc); UNUSED(mode);
  227.     return EINVFN;
  228. }
  229.  
  230. static long ARGS_ON_STACK 
  231. proc_rmdir(dir, name)
  232.     fcookie *dir;
  233.     const char *name;
  234. {
  235.     UNUSED(dir); UNUSED(name);
  236.     return EPTHNF;
  237. }
  238.  
  239. static long ARGS_ON_STACK 
  240. proc_remove(dir, name)
  241.     fcookie *dir;
  242.     const char *name;
  243. {
  244.     PROC *p;
  245.  
  246.     if (dir->index != 0)
  247.         return EPTHNF;
  248.     p = name2proc(name);
  249.     if (!p)
  250.         return EFILNF;
  251.  
  252.     post_sig(p, SIGTERM);
  253.     check_sigs();        /* it might have been us */
  254.     return 0;
  255. }
  256.  
  257. static long ARGS_ON_STACK 
  258. proc_getname(root, dir, pathname)
  259.     fcookie *root, *dir; char *pathname;
  260. {
  261.     PROC *p;
  262.  
  263.     UNUSED(root);
  264.  
  265.     if (dir->index == 0)
  266.         *pathname = 0;
  267.     else {
  268.         p = (PROC *)dir->index;
  269.         ksprintf(pathname, "%s.03d", p->name, p->pid);
  270.     }
  271.     return 0;
  272. }
  273.  
  274. static long ARGS_ON_STACK 
  275. proc_rename(olddir, oldname, newdir, newname)
  276.     fcookie *olddir;
  277.     char *oldname;
  278.     fcookie *newdir;
  279.     const char *newname;
  280. {
  281.     PROC *p;
  282.     int i;
  283.  
  284.     if (olddir->index != 0 || newdir->index != 0)
  285.         return EPTHNF;
  286.     if ((p = name2proc(oldname)) == 0)
  287.         return EFILNF;
  288.  
  289.     oldname = p->name;
  290.     for (i = 0; i < PNAMSIZ; i++) {
  291.         if (*newname == 0 || *newname == '.') {
  292.             *oldname = 0; break;
  293.         }
  294.         *oldname++ = *newname++;
  295.     }
  296.     return 0;
  297. }
  298.  
  299. static long ARGS_ON_STACK 
  300. proc_opendir(dirh, flags)
  301.     DIR *dirh;
  302.     int flags;
  303. {
  304.     UNUSED(flags);
  305.  
  306.     dirh->index = 0;
  307.     return 0;
  308. }
  309.  
  310. static long ARGS_ON_STACK 
  311. proc_readdir(dirh, name, namelen, fc)
  312.     DIR *dirh;
  313.     char *name;
  314.     int namelen;
  315.     fcookie *fc;
  316. {
  317.     int i;
  318.     int giveindex = (dirh->flags == 0);
  319.     PROC *p;
  320.  
  321.     do {
  322.         i = dirh->index++;
  323. /* BUG: we shouldn't have the magic number "1000" for maximum proc pid */
  324.         if (i >= 1000) {
  325.             p = 0;
  326.             break;
  327.         }
  328.         p = pid2proc(i);
  329.     } while (!p);
  330.  
  331.     if (!p)
  332.         return ENMFIL;
  333.  
  334.     fc->index = (long)p;
  335.     fc->fs = &proc_filesys;
  336.     fc->dev = PROC_BASE_DEV | p->pid;
  337.  
  338.     if (giveindex) {
  339.         namelen -= (int)sizeof(long);
  340.         if (namelen <= 0) return ERANGE;
  341.         *((long *)name) = (long)p->pid;
  342.         name += sizeof(long);
  343.     }
  344.     if (namelen < strlen(p->name) + 5)
  345.         return ENAMETOOLONG;
  346.  
  347.     ksprintf(name, "%s.%03d", p->name, p->pid);
  348.     return 0;
  349. }
  350.  
  351. static long ARGS_ON_STACK 
  352. proc_rewinddir(dirh)
  353.     DIR *dirh;
  354. {
  355.     dirh->index = 0;
  356.     return 0;
  357. }
  358.  
  359. static long ARGS_ON_STACK 
  360. proc_closedir(dirh)
  361.     DIR *dirh;
  362. {
  363.     UNUSED(dirh);
  364.     return 0;
  365. }
  366. static long ARGS_ON_STACK 
  367. proc_pathconf(dir, which)
  368.     fcookie *dir;
  369.     int which;
  370. {
  371.     UNUSED(dir);
  372.  
  373.     switch(which) {
  374.     case -1:
  375.         return DP_MAXREQ;
  376.     case DP_IOPEN:
  377.         return UNLIMITED;    /* no internal limit on open files */
  378.     case DP_MAXLINKS:
  379.         return 1;        /* we don't have hard links */
  380.     case DP_PATHMAX:
  381.         return PATH_MAX;    /* max. path length */
  382.     case DP_NAMEMAX:
  383.         return PNAMSIZ;        /* max. length of individual name */
  384.     case DP_ATOMIC:
  385.         return UNLIMITED;    /* all writes are atomic */
  386.     case DP_TRUNC:
  387.         return DP_DOSTRUNC;    /* file names are truncated to 8.3 */
  388.     case DP_CASE:
  389.         return DP_CASEINSENS;    /* case preserved, but ignored */
  390.     default:
  391.         return EINVFN;
  392.     }
  393. }
  394.  
  395. static long ARGS_ON_STACK 
  396. proc_dfree(dir, buf)
  397.     fcookie *dir;
  398.     long *buf;
  399. {
  400.     long size;
  401. /* "sector" size is the size of the smallest amount of memory that can be
  402.    allocated. see mem.h for the definition of ROUND
  403.  */
  404.     long secsiz = ROUND(1);
  405.  
  406.     UNUSED(dir);
  407.  
  408.     size = tot_rsize(core, 0) + tot_rsize(alt, 0);
  409.     *buf++ = size/secsiz;            /* number of free clusters */
  410.     size = tot_rsize(core, 1) + tot_rsize(alt, 1);
  411.     *buf++ = size/secsiz;            /* total number of clusters */
  412.     *buf++ = secsiz;            /* sector size (bytes) */
  413.     *buf = 1;                /* cluster size (in sectors) */
  414.     return 0;
  415. }
  416.  
  417. static DEVDRV * ARGS_ON_STACK 
  418. proc_getdev(fc, devsp)
  419.     fcookie *fc;
  420.     long *devsp;
  421. {
  422.     PROC *p;
  423.  
  424.     p = (PROC *)fc->index;
  425.  
  426.     *devsp = (long)p;
  427.     return &proc_device;
  428. }
  429.  
  430. /*
  431.  * PROC device driver
  432.  */
  433.  
  434. /*
  435.  * BUG: file locking and the O_SHMODE restrictions are not implemented
  436.  * for processes
  437.  */
  438.  
  439. static long ARGS_ON_STACK 
  440. proc_open(f)
  441.     FILEPTR *f;
  442. {
  443.     UNUSED(f);
  444.  
  445.     return 0;
  446. }
  447.  
  448. static long ARGS_ON_STACK 
  449. proc_write(f, buf, nbytes)
  450.     FILEPTR *f; const char *buf; long nbytes;
  451. {
  452.     char *where;
  453.     long bytes_written = 0;
  454.  
  455.     where = (char *)f->pos;
  456.  
  457. /* BUG: process read/writes should check for valid addresses */
  458.  
  459. TRACE(("proc_write: %ld bytes to %lx", nbytes, where));
  460.  
  461.     while (nbytes-- > 0) {
  462.         *where++ = *buf++;
  463.         bytes_written++;
  464.     }
  465.     cpush((void *)f->pos, bytes_written);    /* flush cached data */
  466.     f->pos += bytes_written;
  467.     return bytes_written;
  468. }
  469.  
  470. static long ARGS_ON_STACK 
  471. proc_read(f, buf, nbytes)
  472.     FILEPTR *f; char *buf; long nbytes;
  473. {
  474.     char *where;
  475.     long bytes_read = 0;
  476.  
  477.     where = (char *)f->pos;
  478.  
  479. TRACE(("proc_read: %ld bytes from %lx", nbytes, where));
  480.  
  481.     while (nbytes-- > 0) {
  482.         *buf++ = *where++;
  483.         bytes_read++;
  484.     }
  485.     f->pos += bytes_read;
  486.     return bytes_read;
  487. }
  488.  
  489. /*
  490.  * proc_ioctl: currently, the only IOCTL's available are:
  491.  * PPROCADDR: get address of PROC structure's "interesting" bits
  492.  * PCTXTSIZE: get the size of the CONTEXT structure
  493.  * PBASEADDR: get address of process basepage
  494.  * PSETFLAGS: set the memory allocation flags (e.g. to malloc from fastram)
  495.  * PGETFLAGS: get the memory allocation flags
  496.  * PTRACESFLAGS: set the process tracing flags
  497.  * PTRACEGFLAGS: get the process tracing flags
  498.  * PTRACEGO: restart the process (T1=0/T1=0)
  499.  * PTRACEFLOW: restart the process (T1=0/T0=1)
  500.  * PTRACESTEP: restart the process (T1=1/T0=0)
  501.  * PTRACE11: restart the process (T1=1/T0=1)
  502.  */
  503.  
  504. static long ARGS_ON_STACK 
  505. proc_ioctl(f, mode, buf)
  506.     FILEPTR *f; int mode; void *buf;
  507. {
  508.     PROC *p;
  509.     extern long mcpu;    /* in main.c */
  510.  
  511.     p = (PROC *)f->devinfo;
  512.     switch(mode) {
  513.     case PPROCADDR:
  514.         *((long *)buf) = (long)&p->magic;
  515.         return 0;
  516.     case PBASEADDR:
  517.         *((long *)buf) = (long)p->base;
  518.         return 0;
  519.     case PCTXTSIZE:
  520.         *((long *)buf) = sizeof(CONTEXT);
  521.         return 0;
  522.     case PSETFLAGS:
  523.         /* note: only the low 16 bits are actually used */
  524.         p->memflags = *((long *)buf);
  525.         return 0;
  526.     case PGETFLAGS:
  527.         *((long *)buf) = p->memflags;
  528.         return 0;
  529.     case PTRACESFLAGS:
  530.         if (p->ptracer == curproc || p->ptracer == 0) {
  531.             p->ptraceflags = *(ushort *)buf;
  532.             if (p->ptraceflags == 0) {
  533.                 p->ptracer = 0;
  534.                 p->ctxt[CURRENT].ptrace = 0;
  535.                 p->ctxt[SYSCALL].ptrace = 0;
  536.             }
  537.             else if (p == curproc) {
  538.                 p->ptracer = pid2proc(p->ppid);
  539.             } else {
  540.                 p->ptracer = curproc;
  541.             }
  542.         } else {
  543.             DEBUG(("proc_ioctl: process already being traced"));
  544.             return EACCDN;
  545.         }
  546.         return 0;
  547.     case PTRACEGFLAGS:
  548.         if (p->ptracer == curproc) {
  549.             *(ushort *)buf = p->ptraceflags;
  550.             return 0;
  551.         } else {
  552.             return EACCDN;
  553.         }
  554.     case PTRACE11:
  555.         return EINVFN;
  556.     case PTRACEFLOW:
  557.         if (mcpu < 20) {
  558.             DEBUG(("proc_ioctl: wrong processor"));
  559.             return EINVFN;
  560.         }
  561.         /* fall through */
  562.     case PTRACEGO:
  563.     case PTRACESTEP:
  564.         if (!p->ptracer) {
  565.             DEBUG(("proc_ioctl(PTRACE): process not being traced"));
  566.             return EACCDN;
  567.         }
  568.         else if (p->wait_q != STOP_Q) {
  569.             DEBUG(("proc_ioctl(PTRACE): process not stopped"));
  570.             return EACCDN;
  571.         }
  572.         else if (p->wait_cond &&
  573.             (1L << ((p->wait_cond >> 8) & 0x1f)) & STOPSIGS) {
  574.             DEBUG(("proc_ioctl(PTRACE): process stopped by job control"));
  575.             return EACCDN;
  576.         }
  577.         p->ctxt[SYSCALL].sr &= 0x3fff;    /* clear both trace bits */
  578.         p->ctxt[SYSCALL].sr |= (mode - PTRACEGO) << 14;
  579.         p->sigpending = 0;
  580.         if (buf && *(ushort *)buf != 0) {
  581. TRACE(("PTRACEGO: sending signal %d to pid %d", *(ushort *)buf, p->pid));
  582.             post_sig(p, *(ushort *)buf);
  583.  
  584. /* another SIGNULL hack... within check_sigs() we watch for a pending
  585.  * SIGNULL, if we see this then we allow delivery of a signal to the
  586.  * process, rather than telling the parent.
  587.  */
  588.             p->sigpending |= 1L;
  589.         } else {
  590. TRACE(("PTRACEGO: no signal"));
  591.         }
  592. /* wake the process up */
  593.         rm_q(p->wait_q, p);
  594.         add_q(READY_Q, p);
  595.         return 0;
  596.     case FIONREAD:
  597.     case FIONWRITE:
  598.         *((long *)buf) = 1L;    /* we're always ready for i/o */
  599.         return 0;
  600.     default:
  601.         DEBUG(("procfs: bad Fcntl command"));
  602.     }
  603.     return EINVFN;
  604. }
  605.  
  606. static long ARGS_ON_STACK 
  607. proc_lseek(f, where, whence)
  608.     FILEPTR *f; long where; int whence;
  609. {
  610.     switch(whence) {
  611.     case 0:
  612.         f->pos = where;
  613.         break;
  614.     case 1:
  615.         f->pos += where;
  616.         break;
  617.     case 2:
  618.         f->pos = -where;
  619.         break;
  620.     default:
  621.         return EINVFN;
  622.     }
  623.     return f->pos;
  624. }
  625.  
  626. static long ARGS_ON_STACK 
  627. proc_datime(f, timeptr, rwflag)
  628.     FILEPTR *f;
  629.     short *timeptr;
  630.     int rwflag;
  631. {
  632.     PROC *p;
  633.  
  634.     p = (PROC *)f->devinfo;
  635.     if (rwflag) {
  636.         return EACCDN;
  637.     }
  638.     else {
  639.         *timeptr++ = p->starttime;
  640.         *timeptr = p->startdate;
  641.     }
  642.     return 0;
  643. }
  644.  
  645. static long ARGS_ON_STACK 
  646. proc_close(f, pid)
  647.     FILEPTR *f;
  648.     int pid;
  649. {
  650.     UNUSED(f); UNUSED(pid);
  651.     return 0;
  652. }
  653.